/**
 * Copyright 2011 Tian Zhu <jrash06@gmail.com>
 * 
 * This file is part of java-object-pool.
 *
 * java-object-pool is free software: you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * java-object-pool is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Lesser GNU General Public License for more details.
 *
 * You should have received a copy of the Lesser GNU General Public License
 * along with java-object-pool.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.javaobjectpool;

import java.util.Arrays;

/**
 * The base ArrayPool class for allocating and recycling arrays. subclass should
 * implement createObject() and createObjectArray() method for allocating
 * concrete-typed arrays and eliminating type-checking overhead.
 */
public abstract class ArrayPool<A> {
	/**
	 * the specialized ObjectPool for PoolableArray
	 */
	private static class Pool<T> extends ObjectPool<T> {
		/** length of all PoolableArray cached in this pool */
		private int len;
		/** the ArrayPool object that own this pool */
		private ArrayPool<T> master;

		private Pool(int len, ArrayPool<T> master) {
			super();
			this.len = len;
			this.master = master;
			init();
		}

		@Override
		protected T[] createObjectArray(int len) {
			return master.createArrayStorage(len);
		}

		@Override
		protected T createObject() {
			return master.createArray(len);
		}
	}

	/** have many slots we have initially */
	private static final int INIT_SIZE = 4;

	/** list of pools */
	private volatile Pool<A>[] pools;

	/** constructor that allocates pools */
	@SuppressWarnings("unchecked")
	protected ArrayPool() {
		pools = new Pool[INIT_SIZE];
		int capacity = 1;
		for (int i = 0; i < INIT_SIZE; i++, capacity <<= 1) {
			pools[i] = new Pool<A>(capacity, this);
		}
	}

	/**
	 * override this method to create a concrete-typed array for storage
	 */
	protected abstract A createArray(int len);

	/**
	 * override this method to create array-based storage for pooled arrays
	 */
	protected abstract A[] createArrayStorage(int len);
	
	/**
	 * override this method to do the final clean-up on the recycled array
	 * 
	 * @param o
	 * @param len
	 */
	protected abstract void cleanUp(A o, int len);

	/** allocate logic */
	public final A allocate(int len) {
		int slot = findSlot(len);
		Pool<A> pool = getPoolForAlloc(slot);
		A o = pool.allocate();
		return o;
	}

	/** recycle logic */
	public final void recycle(A o, int len) {
		cleanUp(o,len);
		int slot = findSlot(len);
		pools[slot].recycle(o);
	}

	/** find the slot with slot = ceiling(log_base2(len)) */
	private final int findSlot(int len) {
		if (len == 0) return 0;
		return 32 - Integer.numberOfLeadingZeros(len - 1);
	}

	/** get pool for the slot, ensure it's initialized */
	private final Pool<A> getPoolForAlloc(int slot) {
		// ensure capacity of slots
		ArrayPool.Pool<A>[] observedPool = pools;
		if(slot<observedPool.length) return pools[slot];
		
		synchronized (PoolableArray.class) {
			observedPool = pools;
			if(slot<observedPool.length) return pools[slot];
			
			int oldLength = observedPool.length;
			int newLength = oldLength<<1;
			while(slot>=newLength) newLength <<= 1;
			observedPool = Arrays.copyOf(observedPool, newLength);
			int capacity = 1 << oldLength;
			for (int i = oldLength; i < newLength; i++, capacity <<= 1) {
				observedPool[i] = new ArrayPool.Pool<A>(capacity, this);
			}
			pools = observedPool;
		}

		return pools[slot];
	}

}